说明
虽然我将这篇文章归到 HTML5 API 这一分类中,但确切的说 PWA 并不是单一API,而是对多种API的综合应用。特别的,一个完整的 PWA 需要两个特别的文件——服务线程(一般为 service-worker.js )和Web应用清单(一般为 manifest.json或 manifest.webmanifest ),本文将主要对这两个文件展开介绍。
服务线程(service worker)
服务线程(service worker),是独立与页面的 WebWorker,其在启动后,会一直在后台运营(直到浏览器被关闭,或在一段时间内,没有该应用打开的的页面),并且可以拦截 Web 请求,将本地缓存甚至是即时生成的数据作为相应返回给原页面。
服务线程上下文环境
因为所有页面的请求可能都要经过服务线程,如果服务线程一但被堵塞,将会导致所有页面的请求都被堵塞,所以服务线程中的所有API均为异步API。
服务线程上下文环境是ServiceWorkerGlobalScope的实例,除了ECMAScript定义的语法级对象外,主要还有以下几个属性:
- self: 指上下文环境本身,类似普通浏览器环境下的- window对象,继承自- WorkerGlobalScope
- caches:- Cache API,同普通浏览器环境下的- caches对象,继承自- WorkerGlobalScope
- fetch: 用于网络请求的函数,在服务线程中不支持- XMLHttpRequest,请用- fetch方法函数,继承自- WorkerGlobalScope,但进行了重载
- clients: 其有打开窗口及获取已有窗口等的异步方法
- registration: 当前服务线程的状态等
- skipWaiting: 在服务线程脚本更新后,用于强制重起服务线程
另外有与windows中同名同用法(或功能相似)的对象,均继承自WorkerGlobalScope,具体见下表:
| 对象 | - | - | - | 
|---|---|---|---|
| btoa | location | indexedDB | addEventListener | 
| atob | setTimeout | navigator | removeEventListener | 
| crypto | setInterval | performance | dispatchEvent | 
| fonts | clearTimeout | importScripts | createImageBitmap | 
| origin | clearInterval | queueMicrotask | 
但因为上下文环境的不通,部分API不支持或缺少部分属性成员,如navigator没有keyboard、clipboard、cookieEnabled等成员。
事件
ServiceWorkerGlobalScope 主要有以下几个事件:
- install: 服务线程安装事件
- activate: 服务线程激活事件
- fetch: 页面或其他线程发起网络请求时的事件
- pushsubscriptionchange: 推送订阅改变事件,属- Push API
- push: 推送事件,属- Push API
- message: 消息事件,- postMessage触发的事件
- notificationclick: 通知点击事件,属- Notification API
- notificationclose: 通知关闭事件,属- Notification API
caches
ServiceWorkerGlobalScope.caches 是 CacheStorage 的实例,主要有以下几个方法:
- match(request, options): 返回所有缓存库中第一个与请求匹配的- Response,返回值为- Promise<Response>
- open(name): 打开一个缓存库,返回值为- Promise<Cache>
- delete(name): 删除一个缓存库,返回值为- Promise<boolean>
- has(name): 判断是否存在指定的缓存库,返回值为- Promise<boolean>
- keys(): 获取所有的缓存库名称,返回值为- Promise<string[]>
Cache 实例
通过 ServiceWorkerGlobalScope.caches.open 得到一个 Cache实例,所有的缓存操作均在此对象上实现。其实例主要有以下几个方法:
- match(request, options): 返回缓存库中第一个与请求匹配的- Response,返回值为- Promise<Response>
- matchAll(request, options): 返回缓存库中所有与请求匹配的- Response,返回值为- Promise<Response[]>
- add(request): 将请求及对应的- Response加入到缓存库中,- Response将通过自动- fetch获得
- addAll(requests): 将多个请求及对应的- Response加入到缓存库中,- Response将通过自动- fetch获得
- put(request, response): 将指定的请求及对应相应关联后加入到缓存库中
- delete(request, options): 删除与请求匹配的缓存
install 事件与 activate 事件
由于事件是通过继承EventTarget类实现的,所以,使用传统的方式,均会忽略事件回调函数返回的Promise对象,但在服务线程的安装、激活过程中,可能需要一些异步的操作,例如提前缓存部分页面。为确保触发install、activate 事件后,能够保证相关操作完成后再结束这两种状态,install 事件与 activate 事件提供了 waitUntil 方法。
waitUntil 方法接收一个 Promise 对象作为参数,当该 Promise 对象状态转为 fulfilled,rejected后,install、activate状态才会结束,单如果不调用waitUntil 方法则直接结束。
fetch 事件
fetch 事件中可以劫持其他页面祸线程发起的网络请求,返回一个自定义的相应。
如果需要返回自定义请求,需要劫持原有请求,需要调用fetch 事件的 respondWith 方法。
respondWith 方法接收一个 Promise<Response> 对象作为参数,该Response对象将作为原请求的Response。
此Response对象,可以是在fetch 事件中调用fetch方法得到的Response,也可以是caches中缓存的Response,甚至是可以通过调用Response构造方法创建的Response对象。
其他说明
目前,服务线程只能采用caches和indexedDB两种存储方式。
Web应用清单 (Web App Manifest)
Web应用清单主要提供有关应用程序的信息(例如名称、图标和描述)。本质为JSON文件,其MIME建议为application/manifest+json
其主要有以下几个属性:
- name: 必须,应用的名称
- short_name: 应用的简称,一般用于主屏幕名称
- start_url: 必须,应用的启动地址
- display: 应用的像是方式, 目前支持:- browser: 按照常规网页打开,默认值
- minimal-ui: 只保留控制导航的UI元素
- standalone: 按照普通应用的形式显示
- fullscreen: 全屏显示
 
- background_color: 浏览器自动生产的启动页的背景色,为 CSS颜色值
- description: 应用的描述
- icons: 必须,应用的图标,用于桌面图标及启动页面,是一个数组,每个成员有以下几个属性:- src: 必须,图标的地址
- sizes: 必须,包含空格分隔图像尺寸的字符串
- type: 图标的mine类型
 
- orientation: 显示方向,可以是以下值之一:- any
- natural
- landscape
- landscape-primary
- landscape-secondary
- portrait
- portrait-primary
- portrait-secondary
 
- scope: 定义此网站上下文的导航范围,如果超出这个范围将按照普通页面展示
- theme_color: 网站的默认主题色
启动服务线程并加入清单文件
加入清单文件,只需要一个link标签即可,如:
<link rel="manifest" href="/manifest.json">启动服务线程需要通过JavaScript实现:
if ('serviceWorker' in navigator) {           
  navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
    // 注册成功
    console.log('ServiceWorker registration successful with scope: ', registration.scope);
  }).catch(function (err) {                   
    // 注册失败 :(
    console.log('ServiceWorker registration failed: ', err);
  });
}